package simpleExample;

import gridworld.Environment;

import jade.core.Agent;

import java.awt.Point;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;


public class SimpleAgent extends Agent
{
    public static final String SPOTTER = "spotter";
    public static final String CARRIER = "carrier";
    
    public static final byte NO_KNOWLEDGE = 0;
    public static final byte KNOW_BOMB_LOCATION = 1;
    public static final byte HAS_BOMB = 3;
    
    public static final String BOMB_REQUEST = "bomb";
    public static final String TRAP_REQUEST = "trap";
    
    private static final char MESSAGE_SEPARATOR = ',';

    //0 north, 1 east, 2 south, 3 west
    private int direction = 0;
    
    private final int[] dx = {0, 1, 0, -1};
    private final int[] dy = {-1, 0, 1, 0};
    
    private final int STONE = Integer.MAX_VALUE;
    private final int NOT_VISITED = 0;
    
    private int[][] map;
    
    //the number of positions left to visit on the map
    private int positionsToVisit;

    /** Constructs a Point from a String.
     * The encoding is understand by behaviours associated with this agent.
     * @param content the string
     * @return a Point
     */
    public static Point pointFromString(String content) {
        //we have a new bomb location
        int x = Integer.parseInt(content.
                                 substring(0, content.indexOf(MESSAGE_SEPARATOR)
                                           ));
        int y = Integer.parseInt(content.
                                 substring(content.indexOf(MESSAGE_SEPARATOR) + 
                                           1));
        return new Point(x, y);
    }

     /** Constructs a String from a Point.
      * The encoding is understand by behaviours associated with this agent.
      * @param content the point
      * @return a String
      */
    public static String pointToString(Point content) {
      return "" + (int)content.getX() + MESSAGE_SEPARATOR + (int)content.getY();
    }
    
    /** Returns the Manhattan distance between two points.
     * @param a the first point.
     * @param b the second point.
     * @return the distance as a double.
     */
    public static double manDistance(Point a, Point b) {
        return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY());
    }

    /** Returns the Manhattan distance between two points.
     * @param a the first point.
     * @param x the x-th coordinate of the second point
     * @param y the y-th coordinate of the second point
     * @return the distance as a double
     */
    public static double manDistance(Point a, double x, double y) {
      return Math.abs(a.getX() - x) + Math.abs(a.getY() - y);
    }
    
    /** Returns the nearest point in a set with respect to a reference point,
     * using the Manhattan distance.
     * @param reference the reference point
     * @param points the set of points to be checked.
     * @return a Point
     */
    public static Point getNearestTarget(Point reference, 
                                         Set<Point> points) {
        if(points.isEmpty()) {
            return null;  
        } else {
          Iterator<Point> it = points.iterator();
          Point result = it.next();
          double distance = SimpleAgent.manDistance(result, reference);
          while(it.hasNext()) {
              Point c = it.next();
              double dst = SimpleAgent.manDistance(c, reference);
              if(dst < distance) {
                  distance = dst;
                  result = c;
              }
          }
          return result;
        }
    }

    /** Searches the environment for by moving around.
     */
    public void moveAgent(Point destination) {
        addAllWalls();
        getBestDirectionOnMap(destination);
        if(!translateMove(getAID().getLocalName())) {
            do {
                direction = (1 + direction) % 4;
            //this is async move
            }while(!translateMove(getAID().getLocalName()));
        }
        /*
        if(useBest) {
            //if we can;t get the best direction greedy
            if(!getBestDirectionGreedy(position, destination)) {
                for(int i = 0;i < 10;i++) {
                    getBestDirectionOnMap(position);
                    translateMove(getAID().getLocalName());
                }
                return;
            }
        } else {
            //get the best position to move to based on map.
              getBestDirectionOnMap(position);
        }
        if(!translateMove(getAID().getLocalName())) {
            do {
                //this accounts for the case of another agent being there
                direction = (1 + direction) % 4;
            }while(!translateMove(getAID().getLocalName()));
        };
        */
    }
    
    protected void setup() 
    {
        Object[] arguments = getArguments();
        if(arguments.length < 2) {
            System.err.println("Provide agent type and color");
            doDelete();
        } else {
            Random rand = new Random();
            //facing north
            do {
            }while(!Environment.enter(getAID().getLocalName(), 
                              new Point(rand.nextInt(Environment.getWidth()), 
                                        rand.nextInt(Environment.getHeight())), 
                              arguments[1].toString()));
            //initialize the map
            map = new int[Environment.getHeight()][Environment.getWidth()];
            for (int[] row : map) {
                Arrays.fill(row, NOT_VISITED);
            }
            //initialize the number of position left to visit
            positionsToVisit = Environment.getWidth() * Environment.getHeight();
            if(arguments[0].equals(SPOTTER)) {
                addBehaviour(new SpotterBehaviour(this));
            } else if(arguments[0].equals(CARRIER)) {
                addBehaviour(new CarrierBehaviour(this));
            } else {
                addBehaviour(new MixedBehaviour(this));
            }
        }
    }

    private void addAllWalls() {
        Iterator<Point> it = Environment.senseStones(getAID().getLocalName()).
            iterator();
        while(it.hasNext()) {
            Point p = it.next();
            if(map[(int)p.getY()][(int)p.getX()] == NOT_VISITED) {
              //you can't visit this anyway
              positionsToVisit--;
            }
            map[(int)p.getY()][(int)p.getX()] = STONE;
        }
    }
    
    private boolean translateMove(String name) {
        Point position = Environment.getPosition(getAID().getLocalName());
        //visit the current position
        if(map[(int)position.getY()][(int)position.getX()] == NOT_VISITED) {
          //decrease the number of positions left to visit
          positionsToVisit--;
        }
        //update this position of the map and add it to tabu list
        map[(int)position.getY()][(int)position.getX()]++;
        switch(direction) {
        case 0: return Environment.north(name);
        case 1:return Environment.east(name);
        case 2:return Environment.south(name);
        case 3:return Environment.west(name);
        default: return false;
        }
    }

    /** Gets the number of positions left to visit for this agent.
     * @return the number of positions left to visit.
     */
    public int getPositionsToVisit() {
        return positionsToVisit;
    }
    
    private void getBestDirectionOnMap(Point destination) {
        Point position = Environment.getPosition(getAID().getLocalName());
        int min = Integer.MAX_VALUE;
        int  x = 0, y = 0, dst;
        for(int i = 0;i < 4;i++) {
            x = (int)position.getX() + dx[i];
            y = (int)position.getY() + dy[i];
            if(destination == null) {
                dst = 0; 
            } else {
                dst = (int)manDistance(destination, x, y);
            }
            if((x >= 0) && (x < map[0].length) && (y >= 0) && (y < map.length)
            && map[y][x] < STONE){
                if(map[y][x] + dst < min) {
                  min = map[y][x] + dst;
                  direction = i;
                }
            }
        }
    }
    
    private boolean getBestDirectionGreedy(Point position, Point destination) {
        int x = (int)(destination.getX() - position.getX());
        int y = (int)(destination.getY() - position.getY());
        if((x < 0) && 
           (map[(int)position.getY()][(int)position.getX() - 1] != STONE)) {
            direction = 3;
        } else if((x > 0) && 
           (map[(int)position.getY()][(int)position.getX() + 1] != STONE)) {
            direction = 1;
        } else if((y < 0) && 
           (map[(int)position.getY() - 1][(int)position.getX()] != STONE)) {
            direction = 0;
        } else if ((y > 0) && 
           (map[(int)position.getY() + 1][(int)position.getX()] != STONE)) {
            direction = 2;
        } else {
            return false;
        }
        return true;
    }
}
